using System;
using System.IO;
using System.Linq;
using System.Drawing;
using System.Threading;
using Publish.Services;
using System.Collections;
using System.Windows.Forms;
using System.Collections.Generic;

namespace Publish
{
    public partial class SelectFile : Form
    {
        #region Fields

        private readonly int ThreadId = 0;//线程ID

        private String Versions = String.Empty;//最新版本 文件夹名称

        private Boolean IsUpdate = false;//判断是否仅仅是更新，而不发布。

        private Thread bindDirectoryThread;

        private readonly FileService fileService = new FileService();

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        public SelectFile()
        {
            InitializeComponent();
            ThreadId = Thread.CurrentThread.GetHashCode();//线程Id赋值
        }

        #endregion

        #region Event

        #region 窗体事件

        //窗体加载
        private void SelectFile_Load(object sender, EventArgs e)
        {
            Text += String.Format(" [{0}]", Publish.ProjectName);//项目名称赋值

            BindDirectoryThreadStart();//绑定文件树线程
        }

        //窗体改变大小事件
        private void SelectFile_SizeChanged(object sender, EventArgs e)
        {
            //左边 Treeview
            tvLeftDirectory.Size = new Size(Width / 2 - 119, Height - 90);

            //中间 按钮
            var btnLocation = tvLeftDirectory.Width + 71;
            btnReload.Location = new Point(btnLocation, 52);
            btnClear.Location = new Point(btnLocation, 122);
            btnPublish.Location = new Point(btnLocation, 190);

            //右边 Treeview 与 Label
            tvRightDirectory.Location = new Point(btnLocation + 122, tvLeftDirectory.Location.Y);
            tvRightDirectory.Size = tvLeftDirectory.Size;

            lblPublishFile.Location = new Point(tvRightDirectory.Location.X, 7);
        }

        #endregion

        #region 按钮事件

        //发布按钮
        private void btnPublish_Click(object sender, EventArgs e)
        {
            lblMessage.Text = String.Empty;
            Versions = Publish.ProjectName + FolderService.GetFolder();//记录当前版本号， 在完成后保存到数据库
            var desktopFolder = Publish.DesktopPath + Versions;//桌面地址加上 最新版本号
            var fileList = new ArrayList();//发布文件列表

            foreach (TreeNode node in tvLeftDirectory.Nodes)
            {
                fileService.CopyDirectory(node, desktopFolder, IsUpdate, ref fileList);
            }

            //Publish.IsPublish = true;//标记用户点击了发布按钮

            if (IsUpdate)//仅仅更新，不执行发布操作。
            {
                IsUpdate = false;//重置标记
                return;
            }

            if (fileList != null)//未选取任何文件
            {
                var files = fileList.ToArray();
                FolderService.SaveFolder(Versions, 2, String.Join("|", files));//将当前版本号及发布者信息记录到数据库

                fileService.CompressRar(desktopFolder, Publish.DesktopPath, String.Format("{0}.zip", Versions));//生成zip压缩包

                lblMessage.Text = "　Publish succeeded，The file in the desktop - " + Versions;

                PublishFileVersionService.UpdateThreadStart();//更新项目文件版本数据

                PublishService.BindPublishCountThreadStart();//读取当前项目发布次数

                Publish.Broadcast();//发布完成，进行广播，以同步数据
            }
        }

        //清除，同时更新项目文件版本信息
        private void btnClear_Click(object sender, EventArgs e)
        {
            tvRightDirectory.Nodes.Clear();//清除右边数据

            IsUpdate = true;//标记仅仅更新
            btnPublish_Click(sender, e);//更新待发布数据，标记文件已发布

            SelectAllNodes(tvLeftDirectory.Nodes, true);//取消选中左边数据
        }

        //重新加载
        private void btnReload_Click(object sender, EventArgs e)
        {
            BindDirectoryThreadStart();//绑定文件树线程
        }

        #endregion

        //选中结构，将其父节点全部选中
        private void tvProjectDirectory_AfterCheck(object sender, TreeViewEventArgs e)
        {
            var eNode = e.Node;

            if (bindDirectoryThread != null && bindDirectoryThread.ThreadState == ThreadState.Stopped)
            {
                timer1.Enabled = false;
                timer1.Enabled = true;
            }

            if (eNode.Checked)
                CheckParentItem(eNode.Parent); //选中父级节点
        }

        //延时显示待发布文件
        private void timer1_Tick(object sender, EventArgs e)
        {
            ShowTreeViewThreadStart();//显示待发布文件

            timer1.Enabled = false;
        }

        #endregion

        #region Private Method

        #region Thread Method

        /// <summary>
        /// 显示待发布文件树
        /// </summary>
        private void ShowTreeViewThreadStart()
        {
            var showTreeViewThread = new Thread(ShowTreeView) { IsBackground = true };
            showTreeViewThread.Start();//绑定文件树
        }

        /// <summary>
        /// 绑定文件树线程
        /// </summary>
        private void BindDirectoryThreadStart()
        {
            //通过线程绑定树，和展示变更的待发布文件，以解决卡顿问题。
            bindDirectoryThread = new Thread(BindDirectory) { IsBackground = true };
            bindDirectoryThread.Start();//绑定文件树
        }

        #endregion

        /// <summary>
        /// 绑定文件树
        /// </summary>
        private void BindDirectory()
        {
            tvLeftDirectory.Nodes.Clear();//清除右边数据

            #region 绑定文件树

            var rootNode = new TreeNode
            {
                Tag = "folder|",//标记该文件类型为：文件夹,
                Text = Publish.ProjectName,
            };
            AddTreeNode(rootNode, tvLeftDirectory);//跨线程添加 TreeNode

            BindDirectory(rootNode, Publish.Path);

            rootNode.Expand();

            #endregion

            ShowReadyToReleaseFile(tvLeftDirectory.Nodes, Publish.DictionaryList);//显示变更文件

            ShowTreeView();//显示待发布文件
        }

        /// <summary>
        /// 绑定待复制文件树
        /// </summary>
        /// <param name="node"></param>
        /// <param name="dir">目标地址</param>
        private void BindDirectory(TreeNode node, String dir)
        {
            var source = new DirectoryInfo(dir);//目标路径（项目所在路径）

            if (!source.Exists) return;//原路径找不到鸟~~~

            var files = source.GetFiles();//获取文件

            var dirs = source.GetDirectories();//返回路径信息

            foreach (DirectoryInfo info in dirs)
            {//通过递归复制文件夹
                if (info.GetFiles("*", SearchOption.TopDirectoryOnly).Length + info.GetDirectories("*", SearchOption.AllDirectories).Length == 0)
                {//排除空文件夹
                    continue;
                }

                if (Publish.Folder.Contains(info.Name)) continue;//是否为排除文件夹

                var n = new TreeNode
                {
                    Tag = "folder|",//标记该文件类型为：文件夹
                    Text = info.Name,
                };

                AddTreeNode(n, node);//跨线程添加 TreeNode

                BindDirectory(n, info.FullName);//递归寻找文件夹下面的
            }

            foreach (var fileInfo in files)
            {
                if (Publish.FilesExtension.Contains(fileInfo.Extension)) continue;//判断是否为排除的文件类型，若是则跳过该类型文件

                if (Publish.Files.Contains(fileInfo.Name)) continue;//判断是否为排除文件，若是则跳过该文件

                fileInfo.IsReadOnly = false;//因为文件是从VS里面直接复制过来的，处于只读状态

                var directoryName = (fileInfo.DirectoryName ?? String.Empty).Replace(Publish.Path, Publish.ProjectName);//请注意：为了缩减字符串长度，涉及两处修改时需保持结果一致

                var n = new TreeNode
                {
                    Tag = String.Format("file|{0}\\", directoryName),//标记该文件类型为：文件
                    Text = fileInfo.Name,
                    Name = fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss"),
                    ToolTipText = fileInfo.Length.ToString(),
                };

                AddTreeNode(n, node);//跨线程添加 TreeNode
            }
        }

        /// <summary>
        /// 显示变更（待发布）文件
        /// </summary>
        /// <param name="tn"></param>
        /// <param name="dicList"></param>
        private void ShowReadyToReleaseFile(TreeNodeCollection tn, IDictionary<Char, List<Files>> dicList)
        {
            foreach (TreeNode node in tn)
            {
                var split = node.Tag.ToString().Split('|');//文件类型标识

                if (node.Nodes.Count != 0) ShowReadyToReleaseFile(node.Nodes, dicList);//当前节点下还有子节点，继续

                if (split[0] == "folder") continue;//跳过文件夹

                var path = node.FullPath;

                if (String.IsNullOrWhiteSpace(path)) continue; //为空时是文件夹，不在本次逻辑范围

                var key = PublishFileVersionService.GetKey(path);
                List<Files> filesList;
                if (dicList.TryGetValue(key, out filesList))//有值
                {
                    var beforeFiles = filesList.FirstOrDefault(x => x.Path == path);

                    //由于代码（文件）产生细微的变化重新生成后dll并没有大小的改变，从而无法判断文件是否变更
                    if (beforeFiles == null ||
                        (beforeFiles.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") != node.Name &&//文件被修改，也可能是重新编译而未修改
                        beforeFiles.Size != Decimal.Parse(node.ToolTipText)//文件的大小变了，这应该是修改了
                        ))
                    {
                        node.Checked = true; //该文件已经变动或新增文件，可更新
                    }
                }
                else { node.Checked = true; }//新文件
            }
        }

        /// <summary>
        /// 显示项目文件
        /// </summary>
        private void ShowTreeView()
        {
            tvRightDirectory.Nodes.Clear();

            var checkedNode = GetCheckedNode(tvLeftDirectory.Nodes);

            foreach (var node in checkedNode)
            {
                if (node.Parent != null && node.Parent.Checked) continue;// 存在子节点不添加

                var cloneNode = node.Clone() as TreeNode;
                AddTreeNode(cloneNode, tvRightDirectory);//跨线程添加 TreeNode
            }

            FilterCheckedNode(tvLeftDirectory.Nodes);

            tvRightDirectory.ExpandAll();
        }

        /// <summary>
        /// 全选/反选 checkbox
        /// </summary>
        /// <param name="nds"></param>
        /// <param name="selected"></param>
        private void SelectAllNodes(TreeNodeCollection nds, Boolean selected)
        {
            foreach (TreeNode node in nds)
            {
                if (node.Checked == selected)
                {
                    node.Checked = !selected;
                }
                SelectAllNodes(node.Nodes, selected);
            }
        }

        /// <summary>
        /// 选中父级节点
        /// </summary>
        /// <param name="node"></param>
        private void CheckParentItem(TreeNode node)
        {
            if (node == null) return;

            node.Checked = true;

            if (node.Parent != null)
                CheckParentItem(node.Parent); //递归寻找父级节点然后选中
        }

        /// <summary>
        /// 获取选中项
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        private List<TreeNode> GetCheckedNode(TreeNodeCollection collection)
        {
            var list = new List<TreeNode>();
            foreach (TreeNode node in collection)
            {
                if (node.Checked) list.Add(node);

                list.AddRange(GetCheckedNode(node.Nodes));
            }
            return list;
        }

        /// <summary>
        /// 筛选选中节点
        /// </summary>
        /// <param name="collection"></param>
        private void FilterCheckedNode(TreeNodeCollection collection)
        {
            foreach (TreeNode node in collection)
            {
                if (node == null) continue;
                if (!node.Checked)
                {
                    RemoveNode(tvRightDirectory.Nodes, node);//遍历右边
                }
                FilterCheckedNode(node.Nodes);
            }
        }

        /// <summary>
        /// 找到节点并将其移除，如果节点处于未选中状态下（用于显示已选中待发布文件）
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="n"></param>
        private void RemoveNode(TreeNodeCollection collection, TreeNode n)
        {
            foreach (TreeNode node in collection)
            {
                if (node == null) continue;
                if (!node.Checked && node.Text == n.Text)
                {
                    node.Remove();
                }
                RemoveNode(node.Nodes, n);
            }
        }

        #endregion

        #region TreeView 委托事件

        private delegate void AddTreeNodeDelegate(TreeNode node, TreeNode treeNode);
        event AddTreeNodeDelegate AddNode;
        /// <summary>
        /// 委托调用方法
        /// </summary>
        /// <param name="node"></param>
        /// <param name="treeNode"></param>
        internal void AddTreeNode(TreeNode node, TreeNode treeNode)
        {
            int id = Thread.CurrentThread.GetHashCode();
            if (id != ThreadId)
            {
                AddNode = new AddTreeNodeDelegate(AddLvItemHandler);
                try
                {
                    Invoke(AddNode, new Object[] {node, treeNode});
                }
                catch (ObjectDisposedException)
                {
                    //throw;
                }
                catch (InvalidOperationException)
                {

                }
            }
            else AddLvItemHandler(node, treeNode);
        }
        internal void AddLvItemHandler(TreeNode node, TreeNode treeNode)
        {
            treeNode.Nodes.Add(node);
        }

        private delegate void AddTreeViewNodeDelegate(TreeNode node, TreeView treeView);
        event AddTreeViewNodeDelegate AddTreeViewNode;
        /// <summary>
        /// 委托调用方法
        /// </summary>
        /// <param name="node"></param>
        /// <param name="treeView"></param>
        internal void AddTreeNode(TreeNode node, TreeView treeView)
        {
            int id = Thread.CurrentThread.GetHashCode();
            if (id != ThreadId)
            {
                AddTreeViewNode = new AddTreeViewNodeDelegate(AddLvItemHandler);
                Invoke(AddTreeViewNode, new Object[] { node, treeView });
            }
            else AddLvItemHandler(node, treeView);
        }
        internal void AddLvItemHandler(TreeNode node, TreeView treeView)
        {
            treeView.Nodes.Add(node);
        }

        #endregion

    }
}
